package ru.urfc.portal.model.documents;

import static ru.urfc.portal.ui.client.Constants.DEVICE_WITH_NET;
import static ru.urfc.portal.ui.client.Constants.DOCUMENT_SELECTED_DEVICES;

import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Element;
import org.xml.sax.InputSource;

import ru.urfc.portal.Utils;
import ru.urfc.portal.exceptions.InternalException;
import ru.urfc.portal.exceptions.UserException;
import ru.urfc.portal.exceptions.UserGetItemException;
import ru.urfc.portal.model.Client;
import ru.urfc.portal.model.SysObjectWithClient;
import ru.urfc.portal.model.radio.BaseDevice;
import ru.urfc.portal.model.radio.DeviceWithNet;
import ru.urfc.portal.ui.client.Constants;

@Entity
@XmlRootElement(name = Constants.DOCUMENT)
public class Document extends SysObjectWithClient {
	
	public static final String EMPTY_DATA = "<data/>";
	
	@ManyToOne(fetch = FetchType.EAGER)
	private DocumentType type;
	
	@Temporal(TemporalType.DATE)
	public Date issueDate;
	
	@Temporal(TemporalType.DATE)
	public Date closedDate;
	
	@ManyToOne(fetch = FetchType.EAGER)
	private DocumentState state;
	
	@Column(length = 1024)
	private String deviceIds;
	
	@Transient
	private List<DeviceWithNet> selectedDevices = null;

	public String dms;
	public String lc;
	
	@Column(name = "docData")
 	@Basic(fetch = FetchType.LAZY)
 	@Lob
 	private String data;

	@Transient
 	private boolean dataFetched = false;
	
	public String incomingNumber;
	
	@Temporal(TemporalType.DATE)
	public Date incomingDate; 
 	 
	private String outgoingNumber;
	 
	@Temporal(TemporalType.DATE)
	private Date outgoingDate; 
	
 	static class XmlParseException extends InternalException {
 		public XmlParseException(Throwable cause, String messageTemplate, Object... arguments) {
 			super(cause, messageTemplate, arguments);
 		}
 	}
 	 
 	static class XmlTransformException extends InternalException {
 		public XmlTransformException(Throwable cause, String message) {
 			super(cause, message);
 		}
 	}

	public Document() {
		super(null);
	}	
	
	public Document(Client client, DocumentType type, DocumentState state) {
		super(client);
		this.type = type;
		this.state = state;
		this.data = type.getInitialData();
		setOutgoingDate(Utils.truncToDate(new Date()));
	}


	public Document(Client client, BaseDevice device, DocumentType type, Date issueDate, Date closedDate, DocumentState state) {
		super(client);
		this.deviceIds = device == null? "": "" + device.getId();
		this.type = type;
		this.issueDate = issueDate;
		this.closedDate = closedDate;
		this.state = state;
		this.data = type.getInitialData();
		this.incomingDate = new Date();
		this.incomingNumber = "11222333";
	}

	public String getData() {
		dataFetched = true;
		return data;
	}
	
 	@XmlAnyElement
 	public Element getDataAsElement() {
 		if (dataFetched == false || data == null || data.isEmpty()) {
 			return null;
 		}
 		try {
 			DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
 			DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder();
 			org.w3c.dom.Document doc = builder.parse(new InputSource(new StringReader(data)));
 			return doc.getDocumentElement();
 		} catch (Exception e) {
 			throw new XmlParseException(e, "Unable to parse {0}", data);
 		}
	}
	
	
	public List<Long> getDeviceIdsAsList() {
		List<Long> result = new ArrayList<Long>();
		if (deviceIds == null || deviceIds.length() == 0) {
			return result;
		}
		for (String id: deviceIds.split(Constants.DOCUMENT_IDS_SEPARATOR)) {
			result.add(new Long(id));
		}
		return result;
	}

	@XmlElement(name = "deviceIds")
	public String getDeviceIds() {
		return deviceIds;
	}

	public Document update(DocumentUpdate documentUpdate) {
		deviceIds = documentUpdate.getDeviceIds();
		outgoingNumber = documentUpdate.getOutgoingNumber();
		outgoingDate = documentUpdate.getOutgoingDate();
		data = getDataAsString(documentUpdate.getDataElement());
		return this;
	}

	private String getDataAsString(Element dataElement) {
		if (dataElement == null) {
			return EMPTY_DATA;
		}
		try {
	 		TransformerFactory transformerFactory = TransformerFactory.newInstance();
	 		StringWriter writer = new StringWriter();
			Transformer transformer = transformerFactory.newTransformer();
			transformer.transform(new DOMSource(dataElement), new StreamResult(writer));
	 		return writer.toString();
		} catch (Exception e) {
			throw new XmlTransformException(e, "Unable serialize to string");
		}
	}

	public DocumentType getType() {
		return type;
	}
	
	public void setType(DocumentType type) {
		this.type = type;
	}
	
	public DocumentState getState() {
		return state;
	}
	
	public void setState(DocumentState state) {
		this.state = state;
	}
	
	public String getOutgoingNumber() {
		return outgoingNumber;
	}

	public void setOutgoingNumber(String outgoingNumber) {
		this.outgoingNumber = outgoingNumber;
	}

	public Date getOutgoingDate() {
		return outgoingDate;
	}

	public void setOutgoingDate(Date outgoingDate) {
		this.outgoingDate = outgoingDate;
	}

	public Document check(Long clientId) {
		if (getClient().getId().equals(clientId)) {
			return this;
		}
		throw new UserGetItemException(getClass());
	}

	@XmlElementWrapper(name = DOCUMENT_SELECTED_DEVICES)
	@XmlElement(name = DEVICE_WITH_NET)
	public List<DeviceWithNet> getSelectedDevices() {
		return selectedDevices;
	}

	public void setSelectedDevices(List<DeviceWithNet> selectedDevices) {
		this.selectedDevices = selectedDevices;
	}
	 
 	public void checkEditable() {
		if (!getState().getCode().equals(DocumentState.DRAFT_CODE)) {
			throw new UserException("Нельзя редактировать документ со статусом {0}", getState().getText());
		}
		
	}
	
}
